#include "esp_camera.h"
#include <WiFi.h>
#include <WiFiManager.h>      // Add WiFiManager library
#include "FS.h"               // 文件系统库
#include "SD_MMC.h"           // SD 卡库
#include "soc/soc.h"          // 关闭 Brownout Detector
#include "soc/rtc_cntl_reg.h" // 关闭 Brownout Detector
#include "driver/rtc_io.h"    // rtc_gpio_hold_dis
#include <vector>             // Include the vector header


#define CAMERA_MODEL_AI_THINKER 


#include "camera_pins.h"

#define FLASH_GPIO_NUM 4


void startCameraServer();

// =====================
// SD Card Recording Settings
// =====================
#define MAX_FILE_SIZE_MB 10 // 每个视频文件的最大大小 (MB) - 改为 10MB
#define MIN_FREE_SPACE_MB 100 // SD卡上需要保留的最小可用空间 (MB)
bool isRecording = false;
int currentFileIndex = 0; // 用于追踪当前正在写入或最后一个写入的文件序号
const char *RECORDING_DIR = "/recordings"; // 录像保存目录

// Task handle for the recording task
TaskHandle_t recordingTaskHandle = NULL;

// Function prototype for the recording task
void recordVideoTask(void *parameter);

// Function prototypes
int findOldestFileIndex(const char* dirPath);
int findNextFileIndex(const char* dirPath);
bool deleteFileByIndex(const char* dirPath, int index);

void setup() {
  // Disable brownout detector
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println("\n--- 系统启动 ---");

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 4000000;
  config.frame_size = FRAMESIZE_HD;
  config.pixel_format = PIXFORMAT_JPEG; // for streaming
  //config.pixel_format = PIXFORMAT_RGB565; // for face detection/recognition
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  
  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if(config.pixel_format == PIXFORMAT_JPEG){
    if(psramFound()){
      Serial.println("检测到 PSRAM");
      config.jpeg_quality = 10;
      config.fb_count = 2;
      config.grab_mode = CAMERA_GRAB_LATEST;
    } else {
      Serial.println("未检测到 PSRAM, 限制帧大小");
      // Limit the frame size when PSRAM is not available
      // config.frame_size = FRAMESIZE_SVGA; // 注释掉这里的修改
      config.fb_location = CAMERA_FB_IN_DRAM;
    }
  } else {
    // Best option for face detection/recognition
    // config.frame_size = FRAMESIZE_240X240; // 注释掉这里的修改
#if CONFIG_IDF_TARGET_ESP32S3
    config.fb_count = 2;
#endif
  }

  // 确保分辨率最终设置为 HD
  config.frame_size = FRAMESIZE_HD;

#if defined(CAMERA_MODEL_ESP_EYE)
  pinMode(13, INPUT_PULLUP);
  pinMode(14, INPUT_PULLUP);
#endif

  // camera init
  Serial.println("初始化摄像头...");
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("摄像头初始化失败，错误码: 0x%x\n", err);
    return;
  }
  Serial.println("摄像头初始化成功");


  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // flip it back
    s->set_brightness(s, 1); // up the brightness just a bit
    s->set_saturation(s, -2); // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  if(config.pixel_format == PIXFORMAT_JPEG){\
    // s->set_framesize(s, FRAMESIZE_QVGA); // 注释掉或删除这里的修改，因为我们希望保持 VGA
  }

#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
  s->set_vflip(s, 1);
  s->set_hmirror(s, 1);
#endif

#if defined(CAMERA_MODEL_ESP32S3_EYE)
  s->set_vflip(s, 1);
#endif

  // --- SD Card Initialization ---
  Serial.println("初始化 SD 卡...");
  // 在许多 ESP32-CAM 板上，GPIO 14 通常用于 SD 卡的 CLK，如果它被拉高，SD 卡可能无法工作
  // 如果遇到问题，尝试解除保持状态
  // rtc_gpio_hold_dis(GPIO_NUM_14);
  if(!SD_MMC.begin()){
      Serial.println("SD 卡挂载失败！请检查 SD 卡和连接。");
      // 可以选择在这里停止或者继续运行但不录制
      // return; // 如果希望在 SD 卡失败时停止
  } else {
    uint8_t cardType = SD_MMC.cardType();
    if(cardType == CARD_NONE){
        Serial.println("未检测到 SD 卡。");
    } else {
      Serial.print("SD 卡类型: ");
      if(cardType == CARD_MMC){
          Serial.println("MMC");
      } else if(cardType == CARD_SD){
          Serial.println("SDSC");
      } else if(cardType == CARD_SDHC){
          Serial.println("SDHC");
      } else {
          Serial.println("未知类型");
      }
      uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
      uint64_t totalBytes = SD_MMC.cardSize();
      uint64_t usedBytes = SD_MMC.usedBytes();
      uint64_t freeBytes = totalBytes - usedBytes;
      Serial.printf("SD 卡大小: %llu MB\n", cardSize);
      Serial.printf("SD 卡可用空间: %llu MB / %llu MB\n", freeBytes / (1024 * 1024), totalBytes / (1024 * 1024));

      // 创建录像保存目录 (如果不存在)
      Serial.printf("检查录像目录: %s\n", RECORDING_DIR);
      if (!SD_MMC.exists(RECORDING_DIR)) {
        Serial.printf("目录 %s 不存在，正在创建...\n", RECORDING_DIR);
        if (SD_MMC.mkdir(RECORDING_DIR)) {
          Serial.printf("目录 %s 创建成功\n", RECORDING_DIR);
        } else {
          Serial.printf("创建目录 %s 失败！\n", RECORDING_DIR);
          // 处理错误，可能无法继续录制
        }
      } else {
         Serial.printf("目录 %s 已存在\n", RECORDING_DIR);
      }

      // --- Start Recording Task ---
      isRecording = true; // 设置录制标志
      Serial.println("启动视频录制任务...");
      xTaskCreatePinnedToCore(
          recordVideoTask,         // Task function
          "RecordVideo",           // Task name
          10000,                   // Stack size (bytes) - adjust as needed
          NULL,                    // Parameter
          1,                       // Task priority
          &recordingTaskHandle,    // Task handle
          0                        // Core ID (0 or 1) - Use core 0 for recording
      );
    }
  }

  // --- WiFi and Web Server Setup ---
  // WiFi.begin(ssid, password);
  // WiFi.setSleep(false);

  // WiFiManager
  // Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wm;

  // reset settings - for testing
  // wm.resetSettings();

  // Automatically connect using saved credentials,
  // if connection fails, it starts an access point with the specified name,
  // if empty name is given (""), name becomes ESP + ChipID
  // and goes into a blocking loop awaiting configuration.
  Serial.println("正在设置 WiFi...");
  if (!wm.autoConnect("cp")) {
    Serial.println("连接失败，进入配置模式");
    // You could perhaps restart ESP instead of stalling
    // ESP.restart();
    // delay(5000); // Wait a bit before restarting
  } else {
    Serial.println("WiFi 已连接！");
  }

  startCameraServer();

  Serial.print("Camera Ready! Use 'http://");
  Serial.print(WiFi.localIP());
  Serial.println("' to connect");
}

// Task to record video frames to SD card as MJPEG AVI
void recordVideoTask(void *parameter) {
  Serial.println("录像任务已启动 (AVI 格式) - 基于磁盘空间管理");
  camera_fb_t *fb = NULL;
  File videoFile;
  size_t currentFileSize = 0;
  const size_t maxSizeBytes = MAX_FILE_SIZE_MB * 1024 * 1024;
  char filePath[60]; // Increased buffer size for directory + filename
  char indexPath[60]; // For temporary index file

  // Variables for periodic progress logging
  unsigned long lastLogTime = 0;
  const unsigned long LOG_INTERVAL_MS = 60000; // 1 minute

  // Retry mechanism for write errors
  const int MAX_WRITE_RETRIES = 5;
  int writeRetryCount = 0;
  const int WRITE_RETRY_DELAY_MS = 2000; // 2 seconds

  // --- AVI Header Structures ---
  const uint32_t FRAME_RATE = 10;
  const uint32_t MICROSECONDS_PER_FRAME = 1000000 / FRAME_RATE;

  typedef struct {
    uint32_t offset;
    uint32_t size;
  } AviIndexEntry;

  std::vector<AviIndexEntry> frameIndex;
  uint32_t totalFramesInFile = 0;
  uint32_t moviListOffset = 0;

  // Helper function to write uint32_t in Little Endian
  auto writeUint32LE = [&](File f, uint32_t value) {
    uint32_t pos_before = f.position();
    f.write(value & 0xFF);
    f.write((value >> 8) & 0xFF);
    f.write((value >> 16) & 0xFF);
    f.write((value >> 24) & 0xFF);
    // Serial.printf("  WriteUint32LE: 0x%08X (%u) at %u -> %u\n", value, value, pos_before, f.position());
  };

  // Helper function to write FourCC codes
  auto writeFourCC = [&](File f, const char* code) {
    uint32_t pos_before = f.position();
    f.write((const uint8_t*)code, 4);
    // Serial.printf("  WriteFourCC: %s at %u -> %u\n", code, pos_before, f.position());
  };

  // Initialize file index by finding the next available index
  currentFileIndex = findNextFileIndex(RECORDING_DIR);

  while (isRecording) {
    // Check if we need to start a new file
    if (!videoFile || currentFileSize >= maxSizeBytes) {
      if (videoFile) {
        // --- Finalize the previous AVI file ---
        Serial.println("--- 开始最终化前一个 AVI 文件 ---");
        uint32_t now = videoFile.position();
        Serial.printf("最终化: 当前位置 (movi 数据结束): %u\n", now);

        // Write index chunk ('idx1')
        Serial.println("最终化: 写入 'idx1' 块...");
        writeFourCC(videoFile, "idx1");
        uint32_t indexSize = totalFramesInFile * 16;
        Serial.printf("最终化: 写入 idx1 大小: %u (%d 帧 * 16 字节)\n", indexSize, totalFramesInFile);
        writeUint32LE(videoFile, indexSize);
        for (const auto& entry : frameIndex) {
          writeFourCC(videoFile, "00dc");
          writeUint32LE(videoFile, 0x10);
          writeUint32LE(videoFile, entry.offset);
          writeUint32LE(videoFile, entry.size);
        }
        uint32_t finalSize = videoFile.position();
        Serial.printf("最终化: 写入 idx1 后位置: %u, 计算总文件大小: %u\n", finalSize, finalSize);

        // --- Update RIFF and movi list sizes ---
        Serial.println("最终化: 更新 RIFF 和 movi 块大小...");
        videoFile.seek(4);
        Serial.printf("最终化: seek 到 4, 写入 RIFF 大小: %u\n", finalSize - 8);
        writeUint32LE(videoFile, finalSize - 8);

        videoFile.seek(moviListOffset + 4);
        uint32_t moviDataSize = now - (moviListOffset + 8);
        Serial.printf("最终化: seek 到 %u, 写入 movi 列表数据大小: %u\n", moviListOffset + 4, moviDataSize);
        writeUint32LE(videoFile, moviDataSize);

        // Update total frames in avih header
        videoFile.seek(32);
        Serial.printf("最终化: seek 到 32 (avih.dwTotalFrames), 写入总帧数: %u\n", totalFramesInFile);
        writeUint32LE(videoFile, totalFramesInFile);

        // Update total frames (length) in strh header
        uint32_t strhLenOffset = 32 + 56 + 8 + 8 + 40; // Recalculate just in case
        videoFile.seek(strhLenOffset);
        Serial.printf("最终化: seek 到 %u (strh.dwLength), 写入总帧数: %u\n", strhLenOffset, totalFramesInFile);
        writeUint32LE(videoFile, totalFramesInFile);

        videoFile.close();
        Serial.printf("已关闭视频文件: %s (%d 帧, %d 字节)\n", filePath, totalFramesInFile, finalSize);
        frameIndex.clear();
        totalFramesInFile = 0;
        Serial.println("--- 完成最终化 ---");
      }

      // --- Check and manage disk space BEFORE creating new file ---
      uint64_t totalBytes = SD_MMC.cardSize();
      uint64_t usedBytes = SD_MMC.usedBytes();
      uint64_t freeBytes = totalBytes - usedBytes;
      const uint64_t minFreeBytesRequired = (uint64_t)MIN_FREE_SPACE_MB * 1024 * 1024;
      Serial.printf("检查磁盘空间: 可用 %llu MB / 总计 %llu MB (需要至少 %d MB)\n", freeBytes / (1024 * 1024), totalBytes / (1024 * 1024), MIN_FREE_SPACE_MB);

      while (freeBytes < minFreeBytesRequired) {
          Serial.println("可用空间不足，尝试删除最旧的视频文件...");
          int oldestIndex = findOldestFileIndex(RECORDING_DIR);
          if (oldestIndex > 0) {
              if (deleteFileByIndex(RECORDING_DIR, oldestIndex)) {
                  // Recalculate free space
                  usedBytes = SD_MMC.usedBytes(); // Update used bytes
                  freeBytes = totalBytes - usedBytes;
                  Serial.printf("删除文件 %d 后，可用空间: %llu MB\n", oldestIndex, freeBytes / (1024 * 1024));
              } else {
                  Serial.println("删除最旧文件失败，可能无法继续录制。");
                  isRecording = false; // Stop recording if deletion fails
                  break; // Exit the while loop
              }
          } else {
              Serial.println("未找到可删除的旧视频文件，无法释放空间。");
              isRecording = false; // Stop recording if no files to delete
              break; // Exit the while loop
          }
      }

      // If after deletion attempts, space is still insufficient, stop.
      if (!isRecording) {
          Serial.println("因磁盘空间不足或删除文件失败，停止录制任务。");
          break; // Exit the main recording loop
      }

      // --- Determine next file index and open new file ---
      currentFileIndex = findNextFileIndex(RECORDING_DIR);
      snprintf(filePath, sizeof(filePath), "%s/video_%03d.avi", RECORDING_DIR, currentFileIndex);
      Serial.printf("准备打开新视频文件 (序号 %d): %s\n", currentFileIndex, filePath);

      videoFile = SD_MMC.open(filePath, FILE_WRITE);
      if (!videoFile) {
        Serial.printf("打开文件 %s 用于写入失败！\n", filePath);
        isRecording = false; // Stop recording on error
        break;
      }
      currentFileSize = 0; // Reset file size counter
      Serial.println("--- 开始写入 AVI 文件头 ---");
      uint32_t currentPos = 0;

      // --- Write AVI Header ---
      // RIFF Header
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 RIFF\n", currentPos);
      writeFourCC(videoFile, "RIFF");
      writeUint32LE(videoFile, 0); // Placeholder for file size
      writeFourCC(videoFile, "AVI ");

      // List: hdrl (Header List)
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 LIST hdrl\n", currentPos);
      writeFourCC(videoFile, "LIST");
      uint32_t hdrlListSizeOffset = videoFile.position();
      writeUint32LE(videoFile, 0); // Placeholder for hdrl list size
      writeFourCC(videoFile, "hdrl");

      // avih (Main AVI Header) chunk
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 avih\n", currentPos);
      writeFourCC(videoFile, "avih");
      writeUint32LE(videoFile, 56);
      writeUint32LE(videoFile, MICROSECONDS_PER_FRAME);
      writeUint32LE(videoFile, 10000000);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0x10); // AVIF_HASINDEX
      uint32_t avihTotalFramesOffset = videoFile.position(); // Store offset for later update
      writeUint32LE(videoFile, 0); // Total frames placeholder
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 1);
      writeUint32LE(videoFile, 1280 * 720 * 3);
      writeUint32LE(videoFile, 1280);
      writeUint32LE(videoFile, 720);
      writeUint32LE(videoFile, 0); writeUint32LE(videoFile, 0); writeUint32LE(videoFile, 0); writeUint32LE(videoFile, 0);

      // List: strl (Stream List)
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 LIST strl\n", currentPos);
      writeFourCC(videoFile, "LIST");
      uint32_t strlListSizeOffset = videoFile.position();
      writeUint32LE(videoFile, 0); // Placeholder for strl list size
      writeFourCC(videoFile, "strl");

      // strh (Stream Header) chunk
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 strh\n", currentPos);
      writeFourCC(videoFile, "strh");
      writeUint32LE(videoFile, 56);
      writeFourCC(videoFile, "vids");
      writeFourCC(videoFile, "MJPG");
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 1);
      writeUint32LE(videoFile, FRAME_RATE);
      writeUint32LE(videoFile, 0);
      uint32_t strhLengthOffset = videoFile.position(); // Store offset for later update
      writeUint32LE(videoFile, 0); // Length placeholder
      writeUint32LE(videoFile, 1280 * 720 * 3);
      writeUint32LE(videoFile, (uint32_t)-1);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0); writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 1280); writeUint32LE(videoFile, 720);

      // strf (Stream Format) chunk - BITMAPINFOHEADER
      currentPos = videoFile.position(); 
      Serial.printf("文件头: 理论上 strh 结束于 164, 当前文件指针 @%u\n", currentPos);
      // Force seek to the theoretically correct position for strf, overwriting any gap.
      uint32_t theoreticalStrfStart = 164;
      Serial.printf("文件头: 强制 seek 到 %u 准备写入 strf\n", theoreticalStrfStart);
      videoFile.seek(theoreticalStrfStart); 
      currentPos = videoFile.position(); Serial.printf("文件头: seek 后, @%u 写入 strf\n", currentPos);
      writeFourCC(videoFile, "strf");
      writeUint32LE(videoFile, 40);
      writeUint32LE(videoFile, 40);
      writeUint32LE(videoFile, 1280);
      writeUint32LE(videoFile, 720);
      writeUint32LE(videoFile, 1 | (24 << 16));
      writeFourCC(videoFile, "MJPG");
      writeUint32LE(videoFile, 1280 * 720 * 3);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0);
      writeUint32LE(videoFile, 0);

      // Update LIST hdrl size
      uint32_t hdrlListEnd = videoFile.position(); // Position after strf
      videoFile.seek(hdrlListSizeOffset);
      uint32_t hdrlDataSize = 4 + (4 + 4 + 56) + (4 + 4 + 4 + (4 + 4 + 56) + (4 + 4 + 40)); // Corrected: 'hdrl' + avih_chunk + strl_list_chunk
      // 'hdrl'(4) + avih_chunk(4+4+56=64) + LIST_strl_chunk(4+4+4+strh_chunk(4+4+56=64)+strf_chunk(4+4+40=48) = 12+64+48=124) = 4+64+124 = 192
      hdrlDataSize = 192; // Explicitly set correct size
      Serial.printf("文件头: 更新 hdrl 列表大小 @%u, 写入修正后大小: %u\n", hdrlListSizeOffset, hdrlDataSize);
      writeUint32LE(videoFile, hdrlDataSize); // Use CORRECTED theoretical size

      // Update LIST strl size
      uint32_t strlListEnd = hdrlListEnd; // End position is the same
      videoFile.seek(strlListSizeOffset);
      uint32_t strlDataSize = 4 + (4 + 4 + 56) + (4 + 4 + 40); // Corrected: 'strl' + strh_chunk + strf_chunk
      // 'strl'(4) + strh_chunk(4+4+56=64) + strf_chunk(4+4+40=48) = 4+64+48 = 116
      strlDataSize = 116; // Explicitly set correct size
      Serial.printf("文件头: 更新 strl 列表大小 @%u, 写入修正后大小: %u\n", strlListSizeOffset, strlDataSize);
      writeUint32LE(videoFile, strlDataSize); // Use CORRECTED theoretical size

      // Seek back to the position *after* strf was written (where hdrl list actually ended based on logs)
      videoFile.seek(hdrlListEnd); 
      currentPos = videoFile.position(); Serial.printf("文件头: @%u 写入 LIST movi\n", currentPos);

      // List: movi (Movie Data List)
      writeFourCC(videoFile, "LIST");
      moviListOffset = videoFile.position() - 4;
      writeUint32LE(videoFile, 0); // Placeholder for movi list data size
      writeFourCC(videoFile, "movi");

      currentFileSize = videoFile.position(); // Header size is the starting file size
      Serial.printf("--- 完成写入 AVI 文件头, 头大小: %u, movi 列表开始于: %u ---\n", currentFileSize, moviListOffset);
      lastLogTime = millis(); // Reset log timer when new file starts
    }

    // --- Periodic Progress Log ---
    unsigned long currentTime = millis();
    if (currentTime - lastLogTime >= LOG_INTERVAL_MS) {
        if (videoFile) { // Only log if a file is open
            float currentSizeMB = (float)currentFileSize / (1024 * 1024);
            Serial.printf("[进度] 文件: %s, 当前大小: %.2f MB / %d MB\n", filePath, currentSizeMB, MAX_FILE_SIZE_MB);
        }
        lastLogTime = currentTime; // Update last log time
    }

    // Get camera frame buffer
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("摄像头帧捕获失败");
      vTaskDelay(pdMS_TO_TICKS(1000));
      continue;
    }

    // Write frame to the file inside movi list
    if (videoFile && fb->len > 0) {
      uint32_t frameWriteStartPos = videoFile.position();
      uint32_t frameChunkOffset = frameWriteStartPos - (moviListOffset + 8);
      // Serial.printf("写入帧: 文件位置 %u, movi列表数据偏移 %u, 帧大小 %d\n", frameWriteStartPos, frameChunkOffset, fb->len);

      // Write chunk header ('00dc') and size
      writeFourCC(videoFile, "00dc");
      writeUint32LE(videoFile, fb->len);

      // Write the actual JPEG frame data
      size_t bytesWritten = videoFile.write(fb->buf, fb->len);

      // Pad chunk to next WORD boundary if needed (AVI spec)
      bool paddingNeeded = (fb->len % 2 != 0);
      if (paddingNeeded) {
        videoFile.write(0); // Write a single padding byte
        // Serial.println("  添加了1字节填充");
        bytesWritten++;
      }

      if (bytesWritten >= fb->len) { // Check if write was mostly successful
         uint32_t frameWriteEndPos = videoFile.position();
         uint32_t totalChunkSize = frameWriteEndPos - frameWriteStartPos; // Size of '00dc', size field, data, padding
         currentFileSize += totalChunkSize;

        // Store index entry
        AviIndexEntry entry = {frameChunkOffset, fb->len}; // Store offset and ORIGINAL size
        frameIndex.push_back(entry);
        totalFramesInFile++;
        // Serial.printf("  写入成功: %d 字节 (包括填充 %d), 总块大小 %d, 文件总大小 %u, 帧数 %d\n", bytesWritten, paddingNeeded, totalChunkSize, currentFileSize, totalFramesInFile);

        // Reset retry count on successful write
        writeRetryCount = 0;
      } else {
        Serial.println("SD 卡写入失败！");
        writeRetryCount++;
        if (writeRetryCount <= MAX_WRITE_RETRIES) {
          Serial.printf("尝试重试写入 (%d/%d) in %d ms...\n", writeRetryCount, MAX_WRITE_RETRIES, WRITE_RETRY_DELAY_MS);
          // No need to close the file yet, just wait and try the next frame
          esp_camera_fb_return(fb); // Return the buffer for the failed frame
          fb = NULL;
          vTaskDelay(pdMS_TO_TICKS(WRITE_RETRY_DELAY_MS));
          continue; // Continue to the next loop iteration to get a new frame
        } else {
          Serial.printf("SD 卡写入失败次数已达上限 (%d)，停止录制。\n", MAX_WRITE_RETRIES);
          videoFile.close(); // Close the file on error
          isRecording = false; // Stop recording
          // No need to return fb here as we are stopping
        }
      }
    } else if (fb->len == 0) {
         Serial.println("警告：捕获到空帧。");
    }

    // Return frame buffer
    esp_camera_fb_return(fb);
    fb = NULL;

    // Delay
    vTaskDelay(pdMS_TO_TICKS(100)); // Adjust for desired FPS (~10 FPS here)
  }

  // --- Finalize the last AVI file after loop exits ---
  if (videoFile) {
      Serial.println("--- 停止录制, 开始最终化最后一个 AVI 文件 ---");
      uint32_t now = videoFile.position();
      Serial.printf("最终化: 当前位置 (movi 数据结束): %u\n", now);

      // Write index chunk ('idx1')
      Serial.println("最终化: 写入 'idx1' 块...");
      writeFourCC(videoFile, "idx1");
      uint32_t indexSize = totalFramesInFile * 16;
      Serial.printf("最终化: 写入 idx1 大小: %u (%d 帧 * 16 字节)\n", indexSize, totalFramesInFile);
      writeUint32LE(videoFile, indexSize);
      for (const auto& entry : frameIndex) {
          writeFourCC(videoFile, "00dc");
          writeUint32LE(videoFile, 0x10);
          writeUint32LE(videoFile, entry.offset);
          writeUint32LE(videoFile, entry.size);
      }
      uint32_t finalSize = videoFile.position();
      Serial.printf("最终化: 写入 idx1 后位置: %u, 计算总文件大小: %u\n", finalSize, finalSize);

      // --- Update RIFF and movi list sizes ---
      Serial.println("最终化: 更新 RIFF 和 movi 块大小...");
      videoFile.seek(4);
      Serial.printf("最终化: seek 到 4, 写入 RIFF 大小: %u\n", finalSize - 8);
      writeUint32LE(videoFile, finalSize - 8);

      videoFile.seek(moviListOffset + 4);
      uint32_t moviDataSize = now - (moviListOffset + 8);
      Serial.printf("最终化: seek 到 %u, 写入 movi 列表数据大小: %u\n", moviListOffset + 4, moviDataSize);
      writeUint32LE(videoFile, moviDataSize);

       // Update total frames in avih header
      // avih chunk starts at offset 20. dwTotalFrames is at offset 44 (Absolute)
      uint32_t avihTotalFramesFinalOffset = 44; // Correct absolute offset for dwTotalFrames
      videoFile.seek(avihTotalFramesFinalOffset);
      Serial.printf("最终化: seek 到 %u (avih.dwTotalFrames), 写入总帧数: %u\n", avihTotalFramesFinalOffset, totalFramesInFile);
      writeUint32LE(videoFile, totalFramesInFile);

      // Update total frames (length) in strh header
      // strh chunk starts at offset 88. dwLength is at offset 132 (Absolute)
      uint32_t strhLengthFinalOffset = 132; // Correct absolute offset for dwLength
      videoFile.seek(strhLengthFinalOffset);
      Serial.printf("最终化: seek 到 %u (strh.dwLength), 写入总帧数: %u\n", strhLengthFinalOffset, totalFramesInFile);
      writeUint32LE(videoFile, totalFramesInFile);

      videoFile.close();
      Serial.printf("已关闭最终视频文件: %s (%d 帧, %d 字节)\n", filePath, totalFramesInFile, finalSize);
      Serial.println("--- 完成最终化 ---");
  }

  Serial.println("录像任务已结束。");
  vTaskDelete(NULL); // Delete this task
}

// Helper function to find the lowest index of video_XXX.avi files
int findOldestFileIndex(const char* dirPath) {
    File dir = SD_MMC.open(dirPath);
    if (!dir || !dir.isDirectory()) {
        Serial.printf("错误：无法打开目录 %s\n", dirPath);
        return -1; // Indicate error
    }

    int minIndex = -1;
    File file = dir.openNextFile();
    while (file) {
        if (!file.isDirectory()) {
            const char* fileName = file.name();
            int num;
            // Check if filename matches pattern "video_XXX.avi"
            if (sscanf(fileName, "video_%d.avi", &num) == 1) {
                if (minIndex == -1 || num < minIndex) {
                    minIndex = num;
                }
            }
        }
        file.close();
        file = dir.openNextFile();
    }
    dir.close();
    return minIndex; // Returns -1 if no matching files found
}

// Helper function to find the highest index + 1 for the next file
int findNextFileIndex(const char* dirPath) {
    File dir = SD_MMC.open(dirPath);
    if (!dir || !dir.isDirectory()) {
        Serial.printf("错误：无法打开目录 %s 用于查找下一个文件序号\n", dirPath);
        return 1; // Default to 1 if directory can't be opened
    }

    int maxIndex = 0; // Start at 0, so next is 1 if empty
    File file = dir.openNextFile();
    while (file) {
        if (!file.isDirectory()) {
            const char* fileName = file.name();
            int num;
            if (sscanf(fileName, "video_%d.avi", &num) == 1) {
                if (num > maxIndex) {
                    maxIndex = num;
                }
            }
        }
        file.close();
        file = dir.openNextFile();
    }
    dir.close();
    return maxIndex + 1; // Return the next available index
}

// Helper function to delete a video file by its index number
bool deleteFileByIndex(const char* dirPath, int index) {
    if (index <= 0) return false;
    char filePathToDelete[60];
    snprintf(filePathToDelete, sizeof(filePathToDelete), "%s/video_%03d.avi", dirPath, index);
    Serial.printf("尝试删除文件: %s\n", filePathToDelete);
    if (SD_MMC.exists(filePathToDelete)) {
        if (SD_MMC.remove(filePathToDelete)) {
            Serial.printf("成功删除文件: %s\n", filePathToDelete);
            return true;
        } else {
            Serial.printf("删除文件失败: %s\n", filePathToDelete);
            return false;
        }
    } else {
        Serial.printf("文件不存在，无法删除: %s\n", filePathToDelete);
        return false; // File doesn't exist, technically not a failure but nothing deleted
    }
}

void loop() {
  // The loop is kept clean as recording happens in the background task.
  // You can add other non-blocking logic here if needed.
  delay(10000);
}
